// UpdateGUI.cpp

#include "StdAfx.h"

#include "UpdateGUI.h"

#include "Common/String/StringConvert.h"
#include "Common/String/StringToInt.h"

#include "Common/System/Error.h"
#include "Common/FileSystem/FileDir.h"

#include "Common/App/AppRegistry.h"

#include "../Explorer/MyMessages.h"

#include "Common/Lang/LangUtils.h"
#include "Common/App/ProgramLocation.h"
#include "Common/String/StringUtils.h"
//#include "../FileManager/resourceGui.h"
#include "../FileManager/resource.h"

#include "CompressDialog.h"
#include "UpdateGUI.h"

#include "resource2.h"


using namespace Common;
using namespace Archive;
using namespace FileSystem;

static const TCHAR *kDefaultSfxModule = _T("7z.sfx");
static const TCHAR *kSFXExtension = _T("exe");

extern void AddMessageToString(CString &dest, const CString &src);

CString HResultToMessage(HRESULT errorCode);

class CThreadUpdating: public CProgressThreadVirt
{
  HRESULT ProcessVirt();
public:
  CCodecs *codecs;
  CUpdateCallbackGUI *UpdateCallbackGUI;
  const Wildcard::CCensor *WildcardCensor;
  CUpdateOptions *Options;
  CThreadUpdating(CProgressThreadVirt::ThreadPoolType * pool) : 
	  CProgressThreadVirt(pool)
  {}
};
 
HRESULT CThreadUpdating::ProcessVirt()
{
  CUpdateErrorInfo ei;
  HRESULT res = Common::Archive::UpdateArchive(codecs, *WildcardCensor, *Options,
     ei, UpdateCallbackGUI, UpdateCallbackGUI);
  ErrorMessage = ei.Message;
  ErrorPath1 = ei.FileName;
  ErrorPath2 = ei.FileName2;
  if (ei.SystemError != S_OK && ei.SystemError != E_FAIL && ei.SystemError != E_ABORT)
    return ei.SystemError;
  return res;
}

static bool IsThereMethodOverride(bool is7z, const CString &propertiesString)
{
  CStringVector strings;
  String::SplitStringBySpace(propertiesString, strings);
  for (size_t i = 0; i < strings.size(); i++)
  {
    const CString &s = strings[i];
    if (is7z)
    {
      const wchar_t *end;
      UINT64 n = String::ConvertStringToUInt64(s, &end);
      if (n == 0 && *end == _T('='))
        return true;
    }
    else
    {
      if (s.GetLength() > 0)
        if (s[0] == _T('m') && s[1] == _T('='))
          return true;
    }
  }
  return false;
}

static void ParseAndAddPropertires(std::vector<Props::CProperty> &properties,
    const CString &propertiesString)
{
  CStringVector strings;
  String::SplitStringBySpace(propertiesString, strings);
  properties.reserve(properties.size() + strings.size());
  for (size_t i = 0; i < strings.size(); i++)
  {
    const CString &parseString = strings[i];
	properties.push_back(Props::CProperty(parseString));
  }
}

static CString GetNumInBytesString(UINT64 v)
{
  TCHAR s[32];
  _ui64tot(v, s, 10);
  size_t len = wcslen(s);
  s[len++] = _T('B');
  s[len] = _T('\0');
  return s;
}

//Maybe it can be moved to Property.h
static void SetOutProperties(
	std::vector<Props::CProperty> &properties,
    bool is7z,
    UINT32 level,
    bool setMethod,
    const CString &method,
    UINT32 dictionary,
    bool orderMode,
    UINT32 order,
    bool solidIsSpecified, UINT64 solidBlockSize,
    bool multiThreadIsAllowed, UINT32 numThreads,
    const CString &encryptionMethod,
    bool encryptHeadersIsAllowed, bool encryptHeaders,
    bool /* sfxMode */)
{
  if (level != (UINT32)(INT32)-1)    
	  properties.push_back(Props::CProperty(Props::EXEC_LEVEL, level));
  if (setMethod)
  {
    if (!method.IsEmpty())      
		properties.push_back(Props::CProperty(is7z ? Props::_7Z_METHOD : Props::OTHER_METHOD, method));
    if (dictionary != (UINT32)(INT32)-1)
    {
      CString name;
      if (is7z)
		  name = Props::_7Z_METHOD;
      if (orderMode)
		  name += Props::BASED_ON_MEMORY_USED_SIZE;
      else
		  name += Props::BASED_ON_DICTIONARY_SIZE;
	  properties.push_back(Props::CProperty(name, GetNumInBytesString(dictionary)));      
    }
    if (order != (UINT32)(INT32)-1)
    {
      CString name;
      if (is7z)
		  name = Props::_7Z_METHOD;
      if (orderMode)
		  name += Props::METHOD_ORDERED;
      else
		  name += Props::METHOD_FAST_BYTES;      
	  properties.push_back(Props::CProperty(name, order));
    }
  }
    
  if (!encryptionMethod.IsEmpty())    
	  properties.push_back(Props::CProperty(Props::ENCRYPTION_METHOD, encryptionMethod));

  if (encryptHeadersIsAllowed)    
	  properties.push_back(Props::CProperty(Props::ENCRYPT_HEADER, encryptHeaders));
  if (solidIsSpecified)    
	properties.push_back(Props::CProperty(Props::BLOCK_SIZE, GetNumInBytesString(solidBlockSize)));
  if (multiThreadIsAllowed)    
	properties.push_back(Props::CProperty(Props::THREAD_NUMBER, numThreads));
}

static HRESULT ShowDialog(
    CCodecs *codecs,
    const Wildcard::CCensor &censor,
    CUpdateOptions &options, CUpdateCallbackGUI *callback, HWND hwndParent)
{
  if (options.Commands.size() != 1)
    throw "It must be one command";
  CString currentDirPrefix;
  #ifndef UNDER_CE
  {
    if (!FileSystem::GetCurrentDirectory(currentDirPrefix))
      return E_FAIL;
    FileSystem::FileName::NormalizeDirPathPrefix(currentDirPrefix);
  }
  #endif

  bool oneFile = false;
  FileSystem::File::CFileInfo fileInfo;
  CString name;
  if (censor.Pairs.size() > 0)
  {
    const Wildcard::CPair &pair = censor.Pairs[0];
    if (pair.Head->IncludeItems.size() > 0)
    {
		const std::shared_ptr<Wildcard::CItem>& item = pair.Head->IncludeItems[0];
      if (item->ForFile)
      {
        name = pair.Prefix;
        for (size_t i = 0; i < item->PathParts.size(); i++)
        {
          if (i > 0)
            name += CHAR_PATH_SEPARATOR;
          name += item->PathParts[i];
        }
        if (fileInfo.Find(name))
        {
          if (censor.Pairs.size() == 1 && pair.Head->IncludeItems.size() == 1)
            oneFile = !fileInfo.IsDir();
        }
      }
    }
  }
    
  CCompressDialog dialog;
  CompressDialog::CInfo &di = dialog.Info;
  dialog.ArcFormats = &codecs->Formats;

  for (size_t i = 0; i < codecs->Formats.size(); i++)
  {
    const CArcInfoEx &ai = codecs->Formats[i];
    if (ai.Name.CompareNoCase(_T("swfc")) == 0)
      if (!oneFile || name.Right(4).CompareNoCase(_T(".swf")) != 0)
        continue;
    if (ai.UpdateEnabled && (oneFile || !ai.KeepName))
      dialog.ArcIndices.push_back(i);
  }
  if (dialog.ArcIndices.size() == 0)
  {
    ShowErrorMessage(_T("No Update Engines"));
    return E_FAIL;
  }

  // di.ArchiveName = options.ArchivePath.GetFinalPath();
  di.ArchiveName = options.ArchivePath.GetPathWithoutExt();
  dialog.OriginalFileName = options.ArchivePath.Prefix + fileInfo.Name;
    
  di.CurrentDirPrefix = currentDirPrefix;
  di.SFXMode = options.SfxMode;
  di.OpenShareForWrite = options.OpenShareForWrite;
  
  if (callback->PasswordIsDefined)
    di.Password = callback->Password;
    
  di.KeepName = !oneFile;
    
	if (dialog.DoModal(hwndParent) != IDOK)
    return E_ABORT;
    
	options.VolumesSizes.assign(di.VolumeSizes.begin(), di.VolumeSizes.end());
  /*
  if (di.VolumeSizeIsDefined)
  {
    MyMessageBox(L"Splitting to volumes is not supported");
    return E_FAIL;
  }
  */
  
  Update::CActionSet &actionSet = options.Commands.front()->ActionSet;
  
  switch(di.UpdateMode)
  {
    case CompressDialog::UpdateMode::kAdd:
      actionSet = Update::kAddActionSet;
      break;
    case CompressDialog::UpdateMode::kUpdate:
      actionSet = Update::kUpdateActionSet;
      break;
    case CompressDialog::UpdateMode::kFresh:
      actionSet = Update::kFreshActionSet;
      break;
    case CompressDialog::UpdateMode::kSynchronize:
      actionSet = Update::kSynchronizeActionSet;
      break;
    default:
      throw 1091756;
  }
  const CArcInfoEx &archiverInfo = codecs->Formats[di.FormatIndex];
  callback->PasswordIsDefined = (!di.Password.IsEmpty());
  if (callback->PasswordIsDefined)
    callback->Password = di.Password;

  options.MethodMode.Properties.clear();

  bool is7z = archiverInfo.Name.CompareNoCase(_T("7z")) == 0;
  bool methodOverride = IsThereMethodOverride(is7z, di.Options);

  SetOutProperties(
      options.MethodMode.Properties,
      is7z,
      di.Level,
      !methodOverride,
      di.Method,
      di.Dictionary,
      di.OrderMode, di.Order,
      di.SolidIsSpecified, di.SolidBlockSize,
      di.MultiThreadIsAllowed, di.NumThreads,
      di.EncryptionMethod,
      di.EncryptHeadersIsAllowed, di.EncryptHeaders,
      di.SFXMode);
  
  options.OpenShareForWrite = di.OpenShareForWrite;
  ParseAndAddPropertires(options.MethodMode.Properties, di.Options);

  if (di.SFXMode)
    options.SfxMode = true;
  options.MethodMode.FormatIndex = di.FormatIndex;

  options.ArchivePath.VolExtension = archiverInfo.GetMainExt();
  if (di.SFXMode)
    options.ArchivePath.BaseExtension = kSFXExtension;
  else
    options.ArchivePath.BaseExtension = options.ArchivePath.VolExtension;
  options.ArchivePath.ParseFromPath(di.ArchiveName);

  Common::Archive::WorkDir::CInfo workDirInfo;
  workDirInfo.Load();
  options.WorkingDir.Empty();
  if (workDirInfo.Mode != Archive::WorkDir::Mode::kCurrent)
  {
    CString fullPath;
	FileSystem::GetFullPathName(di.ArchiveName, fullPath);
	options.WorkingDir = Common::Archive::WorkDir::GetWorkDir(workDirInfo, fullPath);
    FileSystem::CreateComplexDirectory(options.WorkingDir);
  }
  return S_OK;
}

HRESULT UpdateGUI(
    CCodecs *codecs,
    const Wildcard::CCensor &censor,
    CUpdateOptions &options,
    bool showDialog,
    bool &messageWasDisplayed,
    CUpdateCallbackGUI *callback,
	std::shared_ptr<Common::System::Threading::CSimpleWorkerPool> * pool,
    HWND hwndParent)
{
  messageWasDisplayed = false;
  HRESULT hRes(S_OK);
  if (showDialog)
  {
    hRes = ShowDialog(codecs, censor, options, callback, hwndParent);
	if(S_OK != hRes)
		return hRes;
  }
  if (options.SfxMode && options.SfxModule.IsEmpty())
  {
    CString folder;
    if (!App::GetProgramFolderPath(folder))
      folder.Empty();
    options.SfxModule = folder + kDefaultSfxModule;
  }

  CThreadUpdating tu(pool);

  tu.codecs = codecs;

  tu.UpdateCallbackGUI = callback;
  tu.UpdateCallbackGUI->ProgressDialog = &tu.ProgressDialog;
  tu.UpdateCallbackGUI->Init();

  CString title = Lang::LangString(IDS_PROGRESS_COMPRESSING, 0x02000DC0);

  tu.WildcardCensor = &censor;
  tu.Options = &options;
  tu.ProgressDialog.IconID = IDI_ICON;

  hRes = tu.Create(title, hwndParent);
  if(S_OK != hRes)
	return hRes;

  messageWasDisplayed = tu.ThreadFinishedOK &
      tu.ProgressDialog.MessagesDisplayed;
  return tu.Result;
}
